package com.atguigu.gmall.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.client.service.ProductFeignClientService;
import com.atguigu.gmall.entity.*;
import com.atguigu.gmall.dao.ProductRepository;
import com.atguigu.gmall.search.*;
import com.atguigu.gmall.service.SearchService;
import lombok.SneakyThrows;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ProductRepository productRepository;
    @Autowired
    private ProductFeignClientService productFeignClientService;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Override//上架
    public void onSale(Long skuId) {
        //根据skuid获取商品信息，远程调用
        SkuInfo skuInfo = productFeignClientService.getSkuInfoById(skuId);
        Product product = turnToProduct(skuInfo);
        productRepository.save(product);
    }
    @Override//下架
    public void offSale(Long skuId) {
        productRepository.deleteById(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        RedisSerializer<String> stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        //定义一个缓存的key
        String hotKey = "hotScore";
        //为什么要用 zset有序集合 需要实现有序自增
        double count = redisTemplate.opsForZSet().incrementScore(hotKey, "skuId:" + skuId, 1);
        //判断什么时候更新es
        if (count % 6 == 0) {
            //更新es将原有数据中的 "hotScore" : 0 改为最新的
            Optional<Product> optional = productRepository.findById(skuId);
            Product product = optional.get();
            product.setHotScore(Math.round(count));
            //保存到es
            productRepository.save(product);
        }
    }

    @SneakyThrows //lombok 异常捕捉
    @Override
    public SearchResponseVo findProductByKeyWord(SearchParam searchParam){
        SearchResponseVo searchResponseVo = new SearchResponseVo();//创建返回值
        SearchRequest searchRequest = this.buildQueryDsl(searchParam);//生成动态查询dsl语句
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);//执行查询，产出结果

        //设置品牌信息
        List<SearchBrandVo> searchBrandVoList = this.getBrandInfo(searchResponse);
        searchResponseVo.setBrandVoList(searchBrandVoList);

        //设置平台属性信息
        List<SearchPlatformPropertyVo> platformPropertyVoList= this.getPlatformPropertyList(searchResponse);
        searchResponseVo.setPlatformPropertyList(platformPropertyVoList);

        //设置商品信息
        SearchHits hits = searchResponse.getHits();//总数据
        List<Product> products = this.turnHitsToPojo(hits); //数据转换为product对象
        searchResponseVo.setProductList(products);//存入product信息

        //设置分页数据
        Long totalHits = hits.getTotalHits();//总记录数
        searchResponseVo.setTotal(totalHits);
        searchResponseVo.setPageNo(searchParam.getPageNo()==null?1:searchParam.getPageNo()); //设置当前页，没有就默认为第一页
        searchResponseVo.setPageSize(searchParam.getPageSize());
        //计算总页数：总记录数/每页显示数量 + % 是否大于0，大于则页数+1
        Long pages = totalHits / searchParam.getPageSize() + (totalHits % searchParam.getPageSize() > 0 ? 1 : 0);
        searchResponseVo.setTotalPages(pages);//总页数

        return searchResponseVo;//响应数据
    }

    //封装product
    private Product turnToProduct(SkuInfo skuInfo) {
        Product product = new Product();
        if (!ObjectUtils.isEmpty(skuInfo)) {   //判断skuInfo不为空
            product.setId(skuInfo.getId());
            product.setProductName(skuInfo.getSkuName());
            product.setCreateTime(new Date());
            product.setPrice(skuInfo.getPrice().doubleValue());
            product.setDefaultImage(skuInfo.getSkuDefaultImg());
        }
        //根据品牌id获取品牌名称：掉远程
        BaseBrand baseBrand = productFeignClientService.getBrandById(skuInfo.getBrandId());
        if (!ObjectUtils.isEmpty(baseBrand)) {
            product.setBrandId(baseBrand.getId());
            product.setBrandName(baseBrand.getBrandName());
            product.setBrandLogoUrl(baseBrand.getBrandLogoUrl());
        }
        //根据三级分类id，获取分类数据,
        BaseCategoryView categoryView = productFeignClientService.getCategory3ForForeign(skuInfo.getCategory3Id());
        if (!ObjectUtils.isEmpty(categoryView)) {
            product.setCategory1Id(categoryView.getCategory1Id());
            product.setCategory1Name(categoryView.getCategory1Name());
            product.setCategory2Id(categoryView.getCategory2Id());
            product.setCategory2Name(categoryView.getCategory2Name());
            product.setCategory3Id(categoryView.getCategory3Id());
            product.setCategory3Name(categoryView.getCategory3Name());
        }
        //获取平台属性
        List<SkuPlatformPropertyValue> values = skuInfo.getSkuPlatformPropertyValueList();
        List<PlatformPropertyKey> platformPropertyList = productFeignClientService.getPlatformProperty(values);
        //遍历平台属性，赋值平台属性value
        if (!CollectionUtils.isEmpty(platformPropertyList)) {
            List<SearchPlatformProperty> searchPlatformPropertyList = platformPropertyList.stream().map(platformProperty -> {
                SearchPlatformProperty searchPlatformProperty = new SearchPlatformProperty();
                searchPlatformProperty.setPropertyKeyId(platformProperty.getId()); //平台属性Id
                searchPlatformProperty.setPropertyKey(platformProperty.getPropertyKey());  //平台属性名称
                Long propertyValueId = 0L;
                for (SkuPlatformPropertyValue value : values) {
                    if (value.getPropertyKeyId().equals(platformProperty.getId()))
                        propertyValueId = value.getPropertyValueId(); //获取当前sku propertyValueId
                }
                //平台属性值名称  获取平台属性值集合对象
                List<PlatformPropertyValue> propertyValueList = platformProperty.getPropertyValueList();
                for (PlatformPropertyValue propertyValue : propertyValueList) {
                    if (propertyValue.getId().equals(propertyValueId))
                        searchPlatformProperty.setPropertyValue(propertyValue.getPropertyValue());//设置value值
                }
                return searchPlatformProperty;
            }).collect(Collectors.toList());
            product.setPlatformProperty(searchPlatformPropertyList);
        }
        productRepository.save(product);
        return product;
    }

    /*根据查询es的结果获取平台属性的key和value*/
    private List<SearchPlatformPropertyVo> getPlatformPropertyList(SearchResponse searchResponse) {
        ParsedNested platformPropertyAgg = searchResponse.getAggregations().get("platformPropertyAgg");
        ParsedLongTerms propertyKeyIdAgg = platformPropertyAgg.getAggregations().get("propertyKeyIdAgg");
        List<? extends Terms.Bucket> buckets = propertyKeyIdAgg.getBuckets();
        if (!CollectionUtils.isEmpty(buckets)) {
            List<SearchPlatformPropertyVo> searchPlatformPropertyVoList = buckets.stream().map(bucket -> {
                SearchPlatformPropertyVo searchPlatformPropertyVo = new SearchPlatformPropertyVo();
                searchPlatformPropertyVo.setPropertyKeyId(bucket.getKeyAsNumber().longValue());//设置平台属性keyid
                ParsedStringTerms propertyKeyAgg = bucket.getAggregations().get("propertyKeyAgg");
                searchPlatformPropertyVo.setPropertyKey(propertyKeyAgg.getBuckets().get(0).getKeyAsString());//设置平台属性key
                //设置平台属性值
                ParsedStringTerms propertyValueAgg = bucket.getAggregations().get("propertyValueAgg");
                List<? extends Terms.Bucket> propertyValueAggBuckets = propertyValueAgg.getBuckets();
                List<String> valueList = propertyValueAggBuckets.stream().map(item -> {
                    return item.getKeyAsString();
                }).collect(Collectors.toList());
                searchPlatformPropertyVo.setPropertyValueList(valueList);
                return searchPlatformPropertyVo;
            }).collect(Collectors.toList());
            return searchPlatformPropertyVoList;
        }
        return null;
    }

    /*根据查询es的结果获得产品的品牌信息*/
    private List<SearchBrandVo> getBrandInfo(SearchResponse searchResponse) {
        ParsedLongTerms brandIdAgg = searchResponse.getAggregations().get("brandIdAgg");
        List<? extends Terms.Bucket> buckets = brandIdAgg.getBuckets();
        List<SearchBrandVo> searchBrandVoList = buckets.stream().map(bucket -> {
            SearchBrandVo searchBrandVo = new SearchBrandVo();
            searchBrandVo.setBrandId(Long.parseLong(bucket.getKeyAsString()));//设置品牌ID
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brandNameAgg");
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            searchBrandVo.setBrandName(brandName);//设置品牌名称
            ParsedStringTerms brandLogoUrlAgg = bucket.getAggregations().get("brandLogoUrlAgg");
            String brandLogoUrl = brandLogoUrlAgg.getBuckets().get(0).getKeyAsString();
            searchBrandVo.setBrandLogoUrl(brandLogoUrl);
            return searchBrandVo;
        }).collect(Collectors.toList());
        return searchBrandVoList;
    }
    /*把查询es的产品信息封装为对象*/
    private List<Product> turnHitsToPojo(SearchHits hits) {
        SearchHit[] hits1 = hits.getHits();
        List<Product> products = new ArrayList<>();
        for (SearchHit hit : hits1) {
            Product product = JSONObject.parseObject(hit.getSourceAsString(), Product.class);
            //看信息里面是否有高亮信息
            if (hit.getHighlightFields().get("productName") != null) {
                //拿取高亮信息
                Text productName = hit.getHighlightFields().get("productName").getFragments()[0];
                product.setProductName(productName.toString());
            }
            products.add(product);
        }
        return products;

    }

    /*根据查询参数构建查询语句*/
    private SearchRequest buildQueryDsl(SearchParam searchParam) {

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();//构建bool字段
        //一级分类
        if (!StringUtils.isEmpty(searchParam.getCategory1Id()))
            boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id()));
        //二级分类
        if (!StringUtils.isEmpty(searchParam.getCategory2Id()))
            boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id()));
        //三级分类
        if (!StringUtils.isEmpty(searchParam.getCategory3Id()))
            boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id()));
        //3.构造品牌id过滤条件 参数格式brandName=2:三星
        if (!StringUtils.isEmpty(searchParam.getBrandName())){
            String[] split = searchParam.getBrandName().split(":");//截取
            if (split.length == 2)//检查格式
                boolQueryBuilder.filter(QueryBuilders.termQuery("brandId", split[0]));//根据品牌id分类
        }
        //4.构造平台属性过滤条件
        //页面传递数据格式http://search.gmall.com/search.html?keyword=三星&props=23:4G:运行内存&props=24:128G:机身内存
        String[] props = searchParam.getProps();
        if (props != null && props.length > 0){
            for (String prop : props) {
                String[] split = prop.split(":");
                if (split.length==3){
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();   //a.构建bool
                    BoolQueryBuilder childBoolQuery = QueryBuilders.boolQuery();   //c.构建子查询条件bool
                    //d.构造属性id,属性值的must条件
                    childBoolQuery.filter(QueryBuilders.termQuery("platformProperty.propertyKeyId", split[0]));
                    childBoolQuery.filter(QueryBuilders.termQuery("platformProperty.propertyValue", split[1]));
                    boolQuery.must(QueryBuilders.nestedQuery("platformProperty", childBoolQuery, ScoreMode.None));
                    boolQueryBuilder.filter(boolQuery);//添加到主bool中
                }
            }
        }
        //5.构造商品名称查询条件 http://search.gmall.com/search.html?keyword=三星
        if (!StringUtils.isEmpty(searchParam.getKeyword()))
            boolQueryBuilder.must(QueryBuilders.matchQuery("productName",searchParam.getKeyword()).operator(Operator.AND));

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();//构建dsl查询器
        searchSourceBuilder.query(boolQueryBuilder);//添加查询条件

        //7.设置分页 起始页公式：(pageNo-1)*pageSize
        Integer form = (searchParam.getPageNo() -1) * searchParam.getPageSize();
        searchSourceBuilder.from(form);
        searchSourceBuilder.size(searchParam.getPageSize());
        /*
         * 8.设置排序
         * 格式http://search.gmall.com/search.html?keyword=三星&props=23:4G:运行内存&order=2:desc
         * 综合=1 价格=2 ---> hotScore=1 price=2
         * &order=1:desc表示按照热度降序排列
         * 如果格式不正确：http://search.gmall.com/search.html?keyword=三星&props=23:4G:运行内存&order=2
         * 给默认排序规则
         */
        if (!StringUtils.isEmpty(searchParam.getOrder())){
            String[] order = searchParam.getOrder().split(":");
            switch (order[0]){
                case "1":  searchSourceBuilder.sort("hotScore", order[1].equals("desc") ?SortOrder.DESC:SortOrder.ASC);
                    break;
                case "2": searchSourceBuilder.sort("price", order[1].equals("desc") ?SortOrder.DESC:SortOrder.ASC);
                    break;
            }
        }else {
            //默认排序
            searchSourceBuilder.sort("hotScore",SortOrder.DESC);//访问量降序排序
        }
        //9.设置高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("productName");
        searchSourceBuilder.highlighter(highlightBuilder);
        highlightBuilder.preTags("<span style=color:red>");
        highlightBuilder.postTags("</span>");

        //10.设置品牌聚合
        TermsAggregationBuilder brandFiled = AggregationBuilders.terms("brandIdAgg").field("brandId");
        brandFiled.subAggregation(AggregationBuilders.terms("brandLogoUrlAgg").field("brandLogoUrl"));
        brandFiled.subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName"));
        searchSourceBuilder.aggregation(brandFiled);
        //设置平台属性聚合
        NestedAggregationBuilder platformFiled = AggregationBuilders.nested("platformPropertyAgg", "platformProperty");
        TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("propertyKeyIdAgg").field("platformProperty.propertyKeyId").size(10);
        aggregationBuilder.subAggregation(AggregationBuilders.terms("propertyKeyAgg").field("platformProperty.propertyKey").size(10));
        aggregationBuilder.subAggregation(AggregationBuilders.terms("propertyValueAgg").field("platformProperty.propertyValue").size(10));
        platformFiled.subAggregation(aggregationBuilder);
        searchSourceBuilder.aggregation(platformFiled);

        SearchRequest searchRequest = new SearchRequest("product"); //设置查询的索引
        searchRequest.types("info");//设置查询的类型
        return searchRequest.source(searchSourceBuilder);
    }
}
